package mypackage.stream;

import java.io.IOException;
import javax.microedition.io.Datagram;
import javax.microedition.io.DatagramConnection;
import javax.microedition.media.Control;
import javax.microedition.media.Manager;
import javax.microedition.media.MediaException;
import javax.microedition.media.Player;
import javax.microedition.media.PlayerListener;
import javax.microedition.media.protocol.ContentDescriptor;
import javax.microedition.media.protocol.DataSource;
import javax.microedition.media.protocol.SourceStream;
import mypackage.EchoScreen;
import net.rim.device.api.media.control.AudioPathControl;

public class RecvStream implements PlayerListener
{
    private Player mPlayer;
    private SendStream mSendStream;

    private long mStartTime = 0;

    private Datagram mPacket;

    private DatagramConnection mConnection;

    private byte[] recvData;

    private boolean mFirstRead = true;

    private String head = "#!AMR\n";

    private long time = 0;

    private boolean mBuffering = false;
    private boolean mRunning;

    private long mPlayerTs = -1;
    private long mReturnedMs = 0;

    private int totalread = 0;
    // test
    private long startBufferTime = 0;
    private long stopBufferTime = 0;

    private boolean priority_set = false;
    private int mTroncatedPacketSize;
    private byte[] mTroncatedPacketData;

    private int throwPacketNum = 70;
    private boolean throwPacket = false;
    private boolean bufferStop = false;
    private int throwNum = 0;

    private ContentDescriptor mContentDescriptor = new ContentDescriptor(
        "audio/amr");

    private long mReadBytes = 0;

    private byte[] sSilentAmr =
    {(byte) 0x3c, (byte) 0x48, (byte) 0xf5, (byte) 0x1f, (byte) 0x96,
        (byte) 0x66, (byte) 0x79, (byte) 0xe1, (byte) 0xe0, (byte) 0x01,
        (byte) 0xe7, (byte) 0x8a, (byte) 0xf0, (byte) 0x00, (byte) 0x00,
        (byte) 0x00, (byte) 0xc0, (byte) 0x00, (byte) 0x00, (byte) 0x00,
        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
        (byte) 0x00, (byte) 0x00};

    private void reset()
    {
        mReturnedMs = 0;
        mPlayerTs = -1;
        mPlayer = null;
        mStartTime = 0;
        mBuffering = true;
    }

    private SourceStream mInput = new SourceStream()
    {
        public int read(byte[] b, int offset, int length) throws IOException
        {
//            EchoScreen.log("@@@RecvStream=read=" + " offset:" + offset
//                + " length:" + length + " time:"
//                + (System.currentTimeMillis() - time));
//            time = System.currentTimeMillis();

            // linphone reference
            int bytesToReturn = sSilentAmr.length;

            if (mBuffering)
            {
                bytesToReturn = sSilentAmr.length * 8;
            }
            try
            {

                if (!priority_set
                    && Thread.currentThread().getPriority() != Thread.MAX_PRIORITY)
                {
                    Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
                    priority_set = true;
                }
                int lWrittenLenth = 0;

                while (lWrittenLenth <= bytesToReturn && mRunning)
                {
                    long ts = getCurTs();
                    if (mPlayerTs == -1)
                    {
                        mPlayerTs = ts;
                    }
                    byte[] packetData = null;
                    try
                    {
                        long diff;

                        while ((diff = (ts - mPlayerTs)) >= 0)
                        {
                            if (diff > 800)
                            {
                                mPlayerTs = ts - 800;
                            }

                            // throw some packets
                            if ((!throwPacket) && (bufferStop) /* false*/)
                            {
//                                while (!throwPacket)
//                                {
//                                    mConnection.receive(mPacket);
//                                    throwNum++;
//                                    if (throwNum > throwPacketNum)
//                                    {
//                                        throwPacket = true;
//                                    }
//                                }
                            }
                            else
                            {
                                // normal recvData
                                mConnection.receive(mPacket);
                                packetData = mPacket.getData();
                            }

                            if (packetData != null)
                            {
                                mReturnedMs += 20;
                                if (mFirstRead)
                                {

                                    EchoScreen.log("First read enter");
                                    String lAmrHeader = "#!AMR\n";
                                    lWrittenLenth = lAmrHeader.length();
                                    System.arraycopy(
                                        lAmrHeader.getBytes("US-ASCII"), 0, b,
                                        offset, lWrittenLenth);
                                    length = length - lWrittenLenth;
                                    offset += lWrittenLenth;
                                    mFirstRead = false;
                                }

                                if ((length < sSilentAmr.length))
                                {
                                    // special case for end of buffer
                                    System.arraycopy(packetData, 0, b,
                                        offset - 4096, length);
                                    lWrittenLenth += length;
                                    mTroncatedPacketSize = length;
                                    mTroncatedPacketData = packetData;

                                    totalread += lWrittenLenth;
                                    return lWrittenLenth;
                                }
                                else
                                {
                                    if (mTroncatedPacketData != null)
                                    {
                                        // special case for troncated packet
                                        int remain = mTroncatedPacketData.length
                                            - mTroncatedPacketSize;
                                        System.arraycopy(mTroncatedPacketData, mTroncatedPacketSize, b, offset, remain);
                                        lWrittenLenth += remain;
                                        mTroncatedPacketSize = 0;
                                        mTroncatedPacketData = null;
                                        offset += remain;
                                        length -= remain;
                                    }
                                    // +1 because we need to skip the CMR bytes
                                    int datalen = packetData.length;
                                    if (offset > 3200)
                                    {
                                        System.arraycopy(packetData, 0, b, offset - 32*10,
                                            datalen);
                                    }
                                    else
                                    {
                                        System.arraycopy(packetData, 0, b, offset,
                                            datalen);
                                    }

                                    lWrittenLenth += datalen;
                                    length -= datalen;
                                    offset += datalen;
                                }
                            }
                            mPlayerTs += 160;
                        }

                    }
                    catch (Exception e)
                    {
                        EchoScreen.log("Media exp:" + e);
                    }
                    if (packetData == null)
                        Thread.sleep(20);
                }
                if (!mRunning)
                {
                    return -1;
                }
                totalread += lWrittenLenth;
                return lWrittenLenth;
            }
            catch (Throwable e)
            {
                EchoScreen.log("exception:" + e.toString());
                return -1;
            }
            finally
            {
                if (bytesToReturn > sSilentAmr.length)
                {
                    mBuffering = false;
                }
            }
        }

        public ContentDescriptor getContentDescriptor()
        {
            return mContentDescriptor;
        }

        public long getContentLength()
        {
            return -1;
        }

        public int getSeekType()
        {
            return SourceStream.RANDOM_ACCESSIBLE;
        }

        public int getTransferSize()
        {
            return -1;
        }

        public long seek(long where) throws IOException
        {
            return where;
        }

        public long tell()
        {
            EchoScreen.log("RecvStream tell");
            if (mStartTime == 0)
                return 0;
            return(System.currentTimeMillis() - mStartTime);
        }

        public Control getControl(String controlType)
        {
            return null;
        }

        public Control[] getControls()
        {
            return null;
        }
    };

    public RecvStream(DatagramConnection connection)
    {
        mConnection = connection;
    }

    public void stop()
    {
        if (mPlayer == null)
            return;// nothing to stop
        mRunning = false;
        try
        {
            if (mPlayer.getState() == Player.STARTED)
            {
                mPlayer.stop();
            }
            if (mPlayer.getState() != Player.CLOSED)
            {
                mPlayer.close();
            }
        }
        catch (MediaException e)
        {
            EchoScreen.log("RecvStream media stop exception:" + e.toString());
        }
    }

    public void start()
    {
        mRunning = true;
        try
        {
            mPacket = mConnection.newDatagram(mConnection.getMaximumLength());
        }
        catch (IOException e1)
        {
            EchoScreen.log("recv init udp exception:" + e1.toString());
            e1.printStackTrace();
        }

        try
        {
            mPlayer = Manager.createPlayer(new DataSource(null)
            {
                SourceStream[] mStream =
                {mInput};

                public void connect() throws IOException
                {
                    // 1
                    EchoScreen.log("connect");
                }

                public void disconnect()
                {
                    EchoScreen.log("disconnect");
                }

                public String getContentType()
                {
                    // 2
                    EchoScreen.log("getContentType");
                    return "audio/amr";
                    // return "audio/x-pcm";
                }

                public SourceStream[] getStreams()
                {
                    // 5.6
                    EchoScreen.log("getStreams");
                    return mStream;
                }

                public void start() throws IOException
                {
                    // 4
                    EchoScreen.log("start");
                }

                public void stop() throws IOException
                {
                    EchoScreen.log("stop");
                }

                public Control getControl(String controlType)
                {
                    EchoScreen.log("getControl");
                    return null;
                }

                public Control[] getControls()
                {
                    EchoScreen.log("getControls");
                    return null;
                }

            });

            mPlayer.addPlayerListener(this);
            mPlayer.realize();
            AudioPathControl lPathCtr = (AudioPathControl) mPlayer
                .getControl("net.rim.device.api.media.control.AudioPathControl");
            lPathCtr
                .setAudioPath(AudioPathControl.AUDIO_PATH_HANDSFREE/* AUDIO_PATH_HANDSET */);
            mPlayer.prefetch();

            mPlayer.start();

        }
        catch (Throwable e)
        {
            EchoScreen.log("RecvStream meida start exception:" + e.toString());
        }

    }

    private int getCurTs()
    {
        if (mStartTime == 0)
        {
            mStartTime = System.currentTimeMillis();
        }
        return (int) ((System.currentTimeMillis() - mStartTime) * 8);
    }

    private int timeUpdateNum;

    public void playerUpdate(Player arg0, String event, Object eventData)
    {
        // EchoScreen.log("RecvStream playerUpdate event:" + event +
        // " eventData:"
        // + eventData);
        if (event == PlayerListener.BUFFERING_STARTED)
        {
            EchoScreen.log("Player event BUFFERING_STARTED");
            startBufferTime = System.currentTimeMillis();
            mBuffering = true;
            int a = ((int) ((mPlayer.getMediaTime() / 1000.0) * 1.6) - totalread);
            EchoScreen.log("buffer data length:" + a);
        }
        else if (event == PlayerListener.STARTED)
        {
            EchoScreen.log("Player event:" + "start");
        }
        else if (event == PlayerListener.BUFFERING_STOPPED)
        {
            stopBufferTime = System.currentTimeMillis();
            EchoScreen.log("Player BUFFERING_STOPPED" + " total buff length:"
                + totalread + " buffer use time:"
                + (stopBufferTime - startBufferTime));

            bufferStop = true;
        }
        else if (event == "com.rim.timeUpdate")
        {
            timeUpdateNum++;
            EchoScreen.log("Player UpdateTimeEvent:" + timeUpdateNum);
        }
        else if (event == PlayerListener.DEVICE_UNAVAILABLE)
        {
            // pausing both player and recorder
            try
            {
                // already stooped mPlayer.stop();
                mSendStream.getPlayer().stop();
            }
            catch (Throwable e)
            {

            }
        }
        else if (event == PlayerListener.DEVICE_AVAILABLE)
        {
        }
        else if (event == PlayerListener.STOPPED)
        {
            EchoScreen.log("Player event stopped");

        }
        else if (event == PlayerListener.ERROR)
        {
            EchoScreen.log("Player event ERROR code:" + eventData);
        }
    }
}
